package nmrworkflow.framework.executionmodule;

import java.io.File;
import java.io.IOException;
import java.util.LinkedList;

import nmrworkflow.framework.beans.NmrDataSetFactory;
import nmrworkflow.framework.beans.datasets.NmrDataSet;
import nmrworkflow.framework.beans.datatypes.NmrFile;
import nmrworkflow.framework.beans.datatypes.NmrStream;
import nmrworkflow.framework.executionmodule.interfaces.DataProcessor;
import nmrworkflow.framework.executionmodule.interfaces.Executable;
import nmrworkflow.framework.utilities.fileconvertor.DataSetConvertorFactory;

/**
 * A serial circuit of Executables such that data is piped
 * from the InputStream to e1 to e2 to ... en to the OutputStream.  Handles
 * the execution of the Executables and creates a StreamAdapter running in
 * it's own thread for each pair of Input/Output streams when run() is called.
 * 
 * @author rjn
 *
 */
public class ExecutionQueue extends LinkedList<Executable> implements DataProcessor<NmrDataSet, NmrDataSet> {
	
	private NmrDataSet input;
	private NmrDataSet finalDataSet;
	final private Class type = NmrDataSet.class;
	
	public ExecutionQueue()
	{
	}
	
	public ExecutionQueue(LinkedList<Executable> executables)
	{
		super();
		this.addAll(executables);
	}
	
	public void run() throws IOException, InterruptedException
	{
		run(false);
	}
	
	public void run(boolean wait) throws IOException, InterruptedException
	{	
		flatten();
		LinkedList<Executable> executables = this.prepareExecutables();
		
		Executable previous = null;

		for(Executable current: executables)
		{
			if(previous == null)
			{
				previous = current;
				continue;
			}
			
			/*
			 * If we're transferring data from a StreamExecutor to a FileExecutor, we
			 * need to wait for the StreamExecutor to finish writing all the data out
			 * to the file.
			 */
			if(previous instanceof StreamProcessor && current instanceof FileProcessor)
				run(true);
			else
				run(false);
			
			previous = current;
		}
		
		run(wait);
	}
	
	public void flatten()
	{
		LinkedList<Executable> buffer = new LinkedList<Executable>(this);
		
		for(Executable e: this)
		{
			if(e instanceof ExecutionQueue)
			{
				ExecutionQueue q = (ExecutionQueue) e;
				q.flatten();
				buffer.addAll(q);
			}
			else
				buffer.add(e);
		}
		
		this.clear();
		this.addAll(buffer);
	}
	
	private LinkedList<Executable> prepareExecutables() throws IOException
	{
		LinkedList<Executable> buffer = new LinkedList<Executable>();
		DataProcessor previous = null;
		
		NmrFile tempFile1 = new NmrFile(File.createTempFile("nmrworkflow", "tmp"));
		NmrFile tempFile2 = new NmrFile(File.createTempFile("nmrworkflow", "tmp"));
		NmrFile currentTempFile = tempFile1;
		NmrDataSet dataSet = input;
		
		tempFile1.deleteOnExit();
		tempFile2.deleteOnExit();
		
		/*
		 * Create DataSets and add DataSetConvertors
		 */
		for(Executable e: this)
		{
			if(! (e instanceof DataProcessor) )
			{
				buffer.add(e);
				continue;
			}
				
			DataProcessor current = (DataProcessor) e;
			
			if(! current.getInputType().isInstance(dataSet))
			{
				DataProcessor convertor = DataSetConvertorFactory.createDataSetConvertor(dataSet.getClass(), current.getInputType());
				
				convertor.setInput(dataSet);
				dataSet = NmrDataSetFactory.create(current.getInputType());
				convertor.setOutput(dataSet);
				
				buffer.add(convertor);
			}
			
			current.setInput(dataSet);
			dataSet = NmrDataSetFactory.create(current.getOutputType());
			current.setOutput(dataSet);
			
			buffer.add(current);
		}

		/*
		 * Create the actual data objects in the DataSets.  It is assumed the DataSetConvertors
		 * will automatically create additional objects the DataSets need (e.g., the procpar for
		 * the VarianDataSets). 
		 */
		for(Executable current: buffer)
		{
			if( !(current instanceof DataProcessor))
				continue;
			
			if(previous == null)
			{
				previous = (DataProcessor) current;
				continue;
			}
				
			if(previous instanceof FileProcessor || current instanceof FileProcessor)
			{
				previous.getOutput().setData(currentTempFile);
				currentTempFile = (currentTempFile == tempFile1) ? tempFile2 : tempFile1;
			} else
				previous.getOutput().setData(new NmrStream());
			
			previous = (DataProcessor) current;
		}
		
		previous.setOutput(this.finalDataSet);
		
		return buffer;
	}
	
	public void setInput(NmrDataSet input)
	{
		this.input = input;
	}
	
	public void setOutput(NmrDataSet output)
	{
		this.finalDataSet = output;
	}
	
	public NmrDataSet getInput()
	{
		return input;
	}
	
	public NmrDataSet getOutput()
	{
		return finalDataSet;
	}
	
	public Class getInputType()
	{
		return type;
	}
	
	public Class getOutputType()
	{
		return type;
	}
	
	public void enqueue(Executable e)
	{
		this.add(e);
	}
	
	public Executable dequeue()
	{
		return this.removeFirst();
	}

}
